/*  Copyright (C) 2008  Alexander Blyzniuchenko (BlezAlex)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace DC
{
    internal class RequestFileState 
    {
        public string FileName;
        public string TTH;
        public string NickName;
        public Task Task;

        public RequestFileState(string nickName, Task task)
        {
            NickName = nickName;
            Task = task;
        }

        public RequestFileState(string nickName, string fileName, string TTH, Task task)
            : this(nickName, task)
        {
            FileName = fileName;
            this.TTH = TTH;
        }
    }

    internal class DownloadManager
    {
        private readonly HubClient _client;
        private readonly TcpListener _listener;
        private readonly string _tempFolder;
		private readonly List<ClientClient> _pendingConnections = new List<ClientClient>();
        private readonly Dictionary<string, ClientClient> _freeConnections = new Dictionary<string, ClientClient>();
        private readonly Dictionary<string, EventWaitHandle> _waitHandles = new Dictionary<string, EventWaitHandle>();

		private const int ConnectionTimeOut = 15 * 1000; // 30 sec

        public event EventHandler<FileRecivedEventArgs> OnFileRecived;
        public event EventHandler<FileListRecivedEventArgs> OnFileListRecived;

        public DownloadManager(HubClient client, string tempFolder)
        {
            _client = client;
            _tempFolder = tempFolder;
            _listener = new TcpListener(IPAddress.Any, 0);
            _listener.Start();
			_listener.BeginAcceptTcpClient(ClientConnectedCallback, null);
        }

        public void RequestFileList(string nickName)
        {
            RequestFileState state = new RequestFileState(nickName, TaskMonitor.StartTask(nickName, "FileList"));
            ThreadPool.QueueUserWorkItem(RequestFileListThreadFunction, state);
        }

        public void RequestFile(string nickName, string fileName, string TTH)
        {
            RequestFileState state = new RequestFileState(nickName, fileName, TTH, TaskMonitor.StartTask(nickName, fileName));
            ThreadPool.QueueUserWorkItem(RequestFileThreadFunction, state);
        }

        internal FileListing GetFileList(string nickName)
        {
            using (DCStream source = GetFile(nickName + "/files.xml.bz2", 0, -1))
            {
                return FileListing.Load(source);
            }
        }

        internal DCStream GetFile(string path, long startPos, long endPos)
        {
            string nick = path.Substring(0, path.IndexOf("/"));
            string filePath = path.Substring(path.IndexOf("/") + 1);

            ClientClient clientConnection = null;

            try
            {
                if (_waitHandles.ContainsKey(nick))
                    throw new Exception(String.Format("Pending connection to {0} already exist", nick));

                EventWaitHandle myWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
                _waitHandles.Add(nick, myWaitHandle);

                _client.ConnectToMe(nick, Port);

                Trace.WriteLine(string.Format("Waiting for nick '{0}'", nick));

                if (!myWaitHandle.WaitOne(ConnectionTimeOut, false))
                    throw new TimeoutException(String.Format("Waiting for nick '{0}' Time Out", nick));

                Trace.WriteLine(string.Format("Nick {0} recived", nick));
                clientConnection = _freeConnections[nick];

                clientConnection.FileRecived += DownloadManager_OnFileRecived;
                clientConnection.RequestFile(filePath, startPos, endPos);

                if (!myWaitHandle.WaitOne(ConnectionTimeOut, false))
                    throw new TimeoutException(String.Format("File wait time out (nick {0})", nick));

                if (clientConnection.ErrorMessage != null)
                    throw new FileNotFoundException(clientConnection.ErrorMessage);

                Trace.WriteLine(string.Format("File {0} from {1} recived", filePath, nick));
            }
            catch
            {
                _freeConnections.Remove(nick);

                if (clientConnection != null)
                    clientConnection.Dispose();
                throw;
            }
            finally
            {
                _freeConnections.Remove(nick);
                if (_waitHandles.ContainsKey(nick))
                    _waitHandles.Remove(nick);
            }

            return clientConnection.GetFileStream();
        }

        private int Port
        {
            get { return ((IPEndPoint)_listener.Server.LocalEndPoint).Port; }
        }

        private void RequestFileListThreadFunction(object args)
        {
            RequestFileState state = (RequestFileState)args;
            try
            {
                using (DCStream source = GetFile(state.NickName + "/files.xml.bz2", 0, -1))
                {
                    state.Task.Status = TaskStatus.Downloading;
                    FileListing listing = FileListing.Load(source);
                    if (OnFileListRecived != null)
                        OnFileListRecived(this, new FileListRecivedEventArgs(state.NickName, listing));
                    state.Task.Status = TaskStatus.Completed;
                };
            }
            catch (Exception ex)
            {
                state.Task.Status = TaskStatus.Failed;
                state.Task.Text = ex.Message;
                Debug.WriteLine(ex.Message);
            }
        }

        private void RequestFileThreadFunction(object args)
        {
            RequestFileState state = (RequestFileState)args;
            try
            {
                using (DCStream source = GetFile(state.NickName + "/TTH/" + state.TTH, 0, -1))
                {
                    state.Task.Status = TaskStatus.Downloading;
                    string resultFileName = Path.Combine(_tempFolder, state.FileName);
                    using (FileStream output = File.Create(resultFileName))
                    {
                        state.Task.Position = 0;
                        state.Task.Total = source.Length;
                        StreamProcessor.Stream2Stream(source, output, source.Length, state.Task);
                    }
                    if (OnFileRecived != null)
                        OnFileRecived(this, new FileRecivedEventArgs(state.NickName, resultFileName));
                    state.Task.Status = TaskStatus.Completed;
                };
            }
            catch (Exception ex)
            {
                state.Task.Status = TaskStatus.Failed;
                state.Task.Text = ex.Message;
                Debug.WriteLine(ex.Message);
            }
        }

		private void ClientConnectedCallback(IAsyncResult result)
		{
			try
			{
                TcpClient client = _listener.EndAcceptTcpClient(result);
			
				ClientClient clientConnection = new ClientClient(client, _client.MyNick);
				clientConnection.OnClientNickRecived += clientConnection_OnClientNickRecived;
                clientConnection.Run();

				_pendingConnections.Add(clientConnection);
			}
			catch (Exception ex)
			{
                Debug.WriteLine(ex.Message);
			}
	        finally
			{
                _listener.BeginAcceptTcpClient(ClientConnectedCallback, null);
			}
		}

        private void DownloadManager_OnFileRecived(object sender, EventArgs e)
		{
			ClientClient clientConnection = (ClientClient)sender;
			clientConnection.FileRecived -= DownloadManager_OnFileRecived;
            _waitHandles[clientConnection.ClientNick].Set();
		}

        private void clientConnection_OnClientNickRecived(object sender, EventArgs e)
		{
			ClientClient clientConnection = (ClientClient) sender;
			clientConnection.OnClientNickRecived -= clientConnection_OnClientNickRecived;
			_pendingConnections.Remove(clientConnection);
			_freeConnections.Add(clientConnection.ClientNick, clientConnection);
            _waitHandles[clientConnection.ClientNick].Set();
		}
    }
}
